package com.cy.lib.redis.core.aop;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import com.cy.lib.redis.core.annotation.CacheClass;
import com.cy.lib.redis.core.annotation.CacheDelete;
import com.cy.lib.redis.core.annotation.CacheList;
import com.cy.lib.redis.core.annotation.CacheOnly;
import com.cy.lib.redis.core.annotation.CacheSave;
import com.cy.lib.redis.core.annotation.CacheUpdate;
import com.cy.lib.redis.core.util.RedisLuaUtil;
import com.cy.lib.redis.util.EntityUtils;
import com.cy.lib.redis.util.LockUtils;
import java.util.concurrent.locks.ReentrantReadWriteLock;

@Component
@Aspect
@SuppressWarnings({"unchecked", "rawtypes"})
public class CacheAspect {

    private final String CACHELIST = "@annotation(com.cy.lib.redis.core.annotation.CacheList)";
    private final String CACHESAVE = "@annotation(com.cy.lib.redis.core.annotation.CacheSave)";
    private final String CACHEONLY = "@annotation(com.cy.lib.redis.core.annotation.CacheOnly)";
    private final String CACHEUPDATE = "@annotation(com.cy.lib.redis.core.annotation.CacheUpdate)";
    private final String CACHEDELETE = "@annotation(com.cy.lib.redis.core.annotation.CacheDelete)";

    @Autowired
    private RedisTemplate redisTemplate;

    private Logger log = LoggerFactory.getLogger(CacheAspect.class);

    @Around(value = CACHESAVE)
    public Object cacheSaveAround(ProceedingJoinPoint pjp) throws Throwable {
        Method method = getMethod(pjp);
        if (method == null) {
            //TODO 没有获得方法后处理
            Object obj = pjp.proceed();
            return obj;
        }
        CacheClass classAnnotation = pjp.getTarget().getClass().getAnnotation(CacheClass.class);

        if (classAnnotation == null) {
            //数据库查询
            Object obj = pjp.proceed();
            return obj;
        }
        CacheSave cacheSave = method.getAnnotation(CacheSave.class);
        final String nameSpace = classAnnotation.nameSpace();
        final String key = parseKey(cacheSave.key(), method, pjp.getArgs());
        final boolean async = cacheSave.async();
        String objKey = nameSpace + "_obj_" + key;
        ReentrantReadWriteLock rwLock = LockUtils.getRWLock(objKey);
        Object obj = null;
        try {
            rwLock.writeLock().lock();
            //保存到数据库
            obj = pjp.proceed();
            //保存到缓存
            //TODO 保存到redis 要考虑 list 添加的情况  事物处理采取了lua脚本 saveLua.lua
            DefaultRedisScript script = RedisLuaUtil.luaMap.get("saveLua.lua");
            Map<String, String> map = EntityUtils.objectToHash(pjp.getArgs()[0]);
            Iterator<String> keysIt = map.keySet().iterator();
            List<String> KEYS = new ArrayList<String>();
            List<String> VALUES = new ArrayList<String>();
            String mkey = "";
            while (keysIt.hasNext()) {
                mkey = keysIt.next();
                KEYS.add(mkey);
                VALUES.add(map.get(mkey));
            }
            List<Object> keys = new ArrayList<Object>();
            keys.add(objKey);
            keys.add(nameSpace);
            redisTemplate.execute(script, redisTemplate.getValueSerializer(), redisTemplate.getStringSerializer(), keys, KEYS, VALUES);
        } finally {
            rwLock.writeLock().unlock();
        }
        return obj;
    }

    @Around(value = CACHELIST)
    public Object cacheListAround(ProceedingJoinPoint pjp) throws Throwable {
        Method method = getMethod(pjp);
        if (method == null) {
            Object obj = pjp.proceed();
            return obj;
        }
        CacheClass classAnnotation = pjp.getTarget().getClass().getAnnotation(CacheClass.class);

        if (classAnnotation == null) {
            //数据库查询
            Object obj = pjp.proceed();
            return obj;
        }
        CacheList cacheList = method.getAnnotation(CacheList.class);
        final String nameSpace = classAnnotation.nameSpace();
        final Class objCls = cacheList.objCls();
        final CacheList.TYPE listType = cacheList.listType();
        String cachCondition = "";
        String key = "";
        String nameSpaceCondition = nameSpace + "_CONDITION_LIST~key";
        if (listType == CacheList.TYPE.ALL) {
            key = nameSpace + "_ALL_LIST~key";
            nameSpaceCondition = "";
        } else if (listType == CacheList.TYPE.CONDITION) {
            cachCondition = cacheList.condition();
            String conditionKey = cachCondition;
            Object[] args = pjp.getArgs();
            for (int i = 0; i < args.length; i++) {
                String arg = "{#arg" + i + "#}";
                conditionKey = conditionKey.replace(arg, args[i].toString());
            }
            key = nameSpace + "_CONDITION_LIST_" + conditionKey + "~key";
        }
        System.out.println("key:" + key);
        ReentrantReadWriteLock rwLock = LockUtils.getRWLock(key);
        List<Object> keys = new ArrayList<Object>();
        try {
            rwLock.readLock().lock();
            //查询缓存
            keys.add(key);
            DefaultRedisScript searchScript = RedisLuaUtil.luaMap.get("listSearchLua.lua");
            searchScript.setResultType(Byte.class);
            Object objCache = redisTemplate.execute(searchScript, redisTemplate.getStringSerializer(), redisTemplate.getStringSerializer(), keys, "");
            List<List<String>> valueList = (List<List<String>>) objCache;
            if (valueList.size() > 0) {
                List<Object> objList = new ArrayList<Object>();
                final Iterator<List<String>> iterator = valueList.iterator();
                while (iterator.hasNext()) {
                    List<String> list = iterator.next();
                    Iterator<String> it = list.iterator();
                    Map<String, String> hash = new HashMap<String, String>();
                    while (it.hasNext()) {
                        hash.put(it.next(), it.next());
                    }
                    Object e = EntityUtils.hashToObject(hash, objCls);
                    objList.add(e);
                }
                return objList;
            }
        } finally {
            rwLock.readLock().unlock();
        }

        Object obj;
        try {
            rwLock.writeLock().lock();
            //查询数据库
            obj = pjp.proceed();
            //保存到缓存
            //TODO 保存到redis 要考虑 list 添加的情况  事物处理采取了redisLua脚本
            DefaultRedisScript saveScript = RedisLuaUtil.luaMap.get("listSaveLua.lua");
            List<Object> saveObjList = (List<Object>) obj;
            List<String> KEYS = new ArrayList<String>();
            List<List<String>> VALUES = new ArrayList<List<String>>();
            String objKey = "";
            keys.clear();
            keys.add(key);
            for (int i = 0; i < saveObjList.size(); i++) {
                Object args = saveObjList.get(i);
                Map<String, String> map = EntityUtils.objectToHash(args);
                Iterator<String> keysIt = map.keySet().iterator();
                List<String> VALUES_Obj = new ArrayList<String>();
                String mkey = "";
                while (keysIt.hasNext()) {
                    mkey = keysIt.next();
                    if (i == 0) {
                        KEYS.add(mkey);
                    }
                    VALUES_Obj.add(map.get(mkey));
                }
                VALUES.add(VALUES_Obj);
                objKey = nameSpace + "_obj_" + parseKey(cacheList.objKey(), args);
                keys.add(objKey);
            }
            keys.add(nameSpaceCondition);
            System.out.println(nameSpaceCondition);
            redisTemplate.execute(saveScript, redisTemplate.getValueSerializer(), redisTemplate.getStringSerializer(), keys, KEYS, VALUES);
        } finally {
            rwLock.writeLock().unlock();
        }
        return obj;
    }

    @Around(value = CACHEONLY)
    public Object cacheOnlyAround(ProceedingJoinPoint pjp) throws Throwable {
        Method method = getMethod(pjp);
        if (method == null) {
            //TODO 没有获得方法后处理
            Object obj = pjp.proceed();
            return obj;
        }
        //获取类注解
        CacheClass classAnnotation = pjp.getTarget().getClass().getAnnotation(CacheClass.class);
        if (classAnnotation == null) {
            //数据库查询
            Object obj = pjp.proceed();
            return obj;
        }
        //方法注解
        CacheOnly cacheOnly = method.getAnnotation(CacheOnly.class);
        final String nameSpace = classAnnotation.nameSpace();
        final String[] keys = cacheOnly.keys();
        final String[] columns = cacheOnly.columns();
        StringBuffer columnStr = new StringBuffer();
        StringBuffer keysStr = new StringBuffer();
        for (int i = 0; i < columns.length; i++) {
            columnStr.append("_");
            columnStr.append(columns[i]);
            columnStr.append("_");
        }
        for (int i = 0; i < keys.length; i++) {
            keysStr.append("_");
            keysStr.append(parseKey(keys[i], method, pjp.getArgs()));
            keysStr.append("_");
        }
        String objKey = nameSpace + "_obj" + columnStr.toString() + keysStr.toString();
        System.out.println("objKey==" + objKey);
        RedisSerializer hashValueSerializer = redisTemplate.getHashValueSerializer();
        ReentrantReadWriteLock rwLock = LockUtils.getRWLock(objKey);
        try {
            rwLock.readLock().lock();
            //缓存查询
            redisTemplate.setHashValueSerializer(redisTemplate.getHashKeySerializer());
            Map<Object, Object> cacheMap = redisTemplate.opsForHash().entries(objKey);
            System.out.println("cacheMap==" + cacheMap);
            //获取返回值类型
            Signature signature = pjp.getSignature();
            Class returnType = ((MethodSignature) signature).getReturnType();
            if (cacheMap != null && cacheMap.size() != 0) {
                Object cacheObj = EntityUtils.hashToObject(cacheMap, returnType);
                System.out.println("cacheObj==" + cacheObj);
                redisTemplate.setHashValueSerializer(hashValueSerializer);
                return cacheObj;
            }
        } finally {
            rwLock.readLock().unlock();
        }
        Object obj;
        try {
            rwLock.writeLock().lock();
            //数据库查询
            obj = pjp.proceed();
            //保存到缓存
            if (obj != null) {
                Map m = EntityUtils.objectToHash(obj);
                redisTemplate.opsForHash().putAll(objKey, m);
                redisTemplate.setHashValueSerializer(hashValueSerializer);
            }
        } finally {
            rwLock.writeLock().unlock();
        }
        return obj;
    }

    @Around(value = CACHEUPDATE)
    public Object cacheUpdateAround(ProceedingJoinPoint pjp) throws Throwable {
        Method method = getMethod(pjp);
        if (method == null) {
            //TODO 没有获得方法后处理
            Object obj = pjp.proceed();
            return obj;
        }
        CacheClass classAnnotation = pjp.getTarget().getClass().getAnnotation(CacheClass.class);

        if (classAnnotation == null) {
            //数据库更新
            Object obj = pjp.proceed();
            return obj;
        }
        CacheUpdate cacheUpdate = method.getAnnotation(CacheUpdate.class);
        final String nameSpace = classAnnotation.nameSpace();
        final String key = parseKey(cacheUpdate.key(), method, pjp.getArgs());
        final boolean async = cacheUpdate.async();
        ReentrantReadWriteLock rwLock = LockUtils.getRWLock(key);
        String objKey = nameSpace + "_obj_" + key;
        Object obj;
        try {
            rwLock.writeLock().lock();
            //保存到数据库
            obj = pjp.proceed();
            //保存到缓存
            //TODO 保存到redis 要考虑 list 更新的情况  事物处理采取了lua脚本 updateLua.lua
            DefaultRedisScript script = RedisLuaUtil.luaMap.get("updateLua.lua");
            Map<String, String> map = EntityUtils.objectToHash(pjp.getArgs()[0]);
            Iterator<String> keysIt = map.keySet().iterator();
            List<String> KEYS = new ArrayList<String>();
            List<String> VALUES = new ArrayList<String>();
            String mkey = "";
            while (keysIt.hasNext()) {
                mkey = keysIt.next();
                KEYS.add(mkey);
                VALUES.add(map.get(mkey));
            }
            List<Object> keys = new ArrayList<Object>();
            keys.add(objKey);
            keys.add(nameSpace);
            redisTemplate.execute(script, redisTemplate.getValueSerializer(), redisTemplate.getStringSerializer(), keys, KEYS, VALUES);
        } finally {
            rwLock.writeLock().unlock();
        }
        return obj;
    }

    @Around(value = CACHEDELETE)
    public Object cacheDeleteAround(ProceedingJoinPoint pjp) throws Throwable {
        Method method = getMethod(pjp);
        if (method == null) {
            //TODO 没有获得方法后处理
            Object obj = pjp.proceed();
            return obj;
        }
        CacheClass classAnnotation = pjp.getTarget().getClass().getAnnotation(CacheClass.class);

        if (classAnnotation == null) {
            //数据库操作
            Object obj = pjp.proceed();
            return obj;
        }
        CacheDelete cacheDelete = method.getAnnotation(CacheDelete.class);
        final String nameSpace = classAnnotation.nameSpace();
        final String key = parseKey(cacheDelete.key(), method, pjp.getArgs());
        final boolean async = cacheDelete.async();
        String objKey = nameSpace + "_obj_" + key;
        Object obj;
        ReentrantReadWriteLock rwLock = LockUtils.getRWLock(key);
        try {
            rwLock.writeLock().lock();
            //保存到数据库
            obj = pjp.proceed();
            //保存到缓存
            //TODO 保存到redis 要考虑 list 删除的情况  事物处理采取了lua脚本 deleteLua.lua
            DefaultRedisScript script = RedisLuaUtil.luaMap.get("deleteLua.lua");
            Map<String, String> map = EntityUtils.objectToHash(pjp.getArgs()[0]);
            Iterator<String> keysIt = map.keySet().iterator();
            List<String> KEYS = new ArrayList<String>();
            List<String> VALUES = new ArrayList<String>();
            String mkey = "";
            while (keysIt.hasNext()) {
                mkey = keysIt.next();
                KEYS.add(mkey);
                VALUES.add(map.get(mkey));
            }
            List<Object> keys = new ArrayList<Object>();
            keys.add(objKey);
            keys.add(nameSpace);
            redisTemplate.execute(script, redisTemplate.getValueSerializer(), redisTemplate.getStringSerializer(), keys, KEYS, VALUES);
        } finally {
            rwLock.writeLock().unlock();
        }
        return obj;
    }

    /**
     * *
     * 获取拦截方法的对象
     *
     * @param pjp
     * @return
     */
    private Method getMethod(ProceedingJoinPoint pjp) {
        MethodSignature ms = (MethodSignature) pjp.getSignature();
        return ms.getMethod();
    }

    /**
     *
     * @param key
     * @param method
     * @param args
     * @return
     */
    private String parseKey(String key, Method method, Object[] args) {

        if ("".equals(key)) {
            return key;
        }
        if (key.indexOf("#") == -1) {
            return key;
        }

        //获取参数列表
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);

        //解析Spel
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < paraNameArr.length; i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }
        Expression epn = parser.parseExpression(key);
        try {
            key = epn.getValue(context, String.class);
        } catch (EvaluationException ee) {

        }
        return key;
    }

    private String parseKey(String key, Object args) {

        if ("".equals(key)) {
            return key;
        }
        if (key.indexOf("#") == -1) {
            return key;
        }

        //解析Spel
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        String paraName = args.getClass().getSimpleName().toLowerCase();
        context.setVariable(paraName, args);
        Expression epn = parser.parseExpression(key);
        try {
            key = epn.getValue(context, String.class);
        } catch (EvaluationException ee) {

        }
        return key;
    }
}
